Juan M. Fonseca-Solís · Agosto 2020 · 5 min read
Los estetoscopios digitales son herramientas que le permiten a los médicos realizar remotamente un seguimiento de sus pacientes por problemas de salud relacionados, por ejemplo, con el corazón y los pulmones. En muchos de los casos, estos estetoscopios digitales utilizan los mismos codecs que son empleados en sistemas de teleconferencia como Zoom, MS Teams y Skype. El éxito de estos codecs está en que son capaces de transmitir voz y música de alta calidad empleando bajas tasas de bits; para ello se valen de que el oído humano es incapaz de escuchar ciertas frecuencias y por eso las eliminan. Aunque esto resulte excelente para cuestiones de recreación y comunicación, no es tan bueno para los algoritmos de reconocimiento de patrones, que necesitan la información completa para sus algoritmos. En este ipython notebook nos dimos a la tarea de realizar pruebas al códec OPUS para determinar si la pérdida de la calidad es realmente significativa. Los resultados mostraron que...
En comparación a otros algoritmos como el MP3, AAC y Vorbis, el códec OPUS ofrece una calidad de audio similar pero a una menor tasa de bits y es capaz de cubrir casi todo el rango de transmisión (ver figura 1). Esto significa que se puede ofrecer una calidad de audio mejor al mismo costo. Opus está compuesto por dos algoritmos llamados SILK (creado por Skype Limited para comprimir voz) y Constrained Energy Lapped Transform (CELT) (para comprimir música) [3, 4, 13]; así como un modo híbrido.
Figura 1. Tomada de https://opus-codec.org/static/comparison/quality.svg.
La mayoría de códecs de sonido utilizan la teoría psicoacústica para "engañar" al oído humano (más exáctamente, la cóclea) haciendo que perciba la misma calidad de audio a costa de eliminar las frecuencias inaudibles. Para ello, utilizan conceptos tales como las escalas Bark y ERB, bandas críticas, enmascaramiento frecuencial, enmascaramiento temporal y predicción lineal (LPC, por sus siglas en Inglés). A continuación hacemos un resumen de estos conceptos:
Figura 2. Tomada de https://en.wikipedia.org/wiki/Auditory_system#/media/File:Anatomy_of_the_Human_Ear.svg.
%pylab inline
import numpy as np
F = np.linspace(0,20000,1000)
erb = 21.4 * np.log10(0.00437*F+1)
pylab.plot(F/1e3,erb)
pylab.ylabel('Frequency (kHz)')
pylab.xlabel('ERB (kHz)')
pylab.show()
Bandas críticas: segmentos de la cóclea, de diferente longitud y localización, en los cuales el oído humano no es capaz de diferenciar dos tonos lo suficientemente cercanos. Tres ejemplos de bandas críticas están en los rangos $[9, 12]$ kHz, $[12, 15]$ kHz y $[15, 20]$ kHz [10].
Enmascaramiento frecuencial: fenómeno producido las bandas críticas (ver figura 3) donde la frecuencia de mayor energía enmascara a las de menor energía usando su envolvente espectral.
Figura 3. Tomada de https://output.com/blog/9-sound-design-tips-to-hack-your-listeners-ears.
Enmascaramiento temporal: fenómeno que ocurre cuando dos eventos ocurren lo suficientemente cercanos en el tiempo y el más fuerte enmascará al más débil si el segundo ocurre 10 ms antes (pre-enmascaramiento) o 30-60 ms después (post-enmascaramiento) que el primero [7] (ver figura 4).
Figura 4. Tomada de https://d3i71xaburhd42.cloudfront.net/13674722d0e4fc8a6877773b25a62ee9850eb46a/3-Figure2-1.png.
Basándonos en los conceptos de psicoacústica explicados arriba, y considerando que el rango de frecuencias para los sonidos producidos por los pulmones es de $[60,1000]$ Hz y del corazón es de $[20, 500]$ Hz, podemos definir una serie de casos de prueba que podemos correr contra el algoritmo OPUS para determinar si la pérdida de información frecuencial es significativa [14, 15].
Para probar el caso 2 se puede utilizar un barrido de frecuencias que siga la siguiente ecuación [6] $$ F(t) = \Big(\frac{F_1-F_0}{T}\Big)t + F_0, $$
El rango de frecuencias a usar es de $[20, 1000]$ Hz, que es el mismo rango admitido por los equipos electrónicos comerciales y también el rango de audición humana. El barrido puede ser construido usando un senoidal de la forma $x[t] = A \sin(2\pi Ft)$.
Para probar el caso 1, se puede agregar al barrido un tono adicional con energía más baja:
$$ x[t] = \sin(2\pi F_0 t) + 0.2 \sin(2\pi 1.1 F_0 t). $$%pylab inline
from scipy.io import wavfile
from IPython.display import Audio
import numpy as np
def plotSpecgram(x,Fs):
# spectrogram
fig, ax = pylab.subplots(nrows=1)
ax.specgram(x, NFFT=1024, Fs=Fs, noverlap=900)
pylab.xlabel('Tiempo (s)')
pylab.ylabel('Frecuencia (Hz)')
pylab.show()
def plotFFT(x,Fs):
pylab.figure()
N2=int(len(x)/2)
F = np.linspace(0,Fs/2,N2)/1000
X = np.sqrt(np.abs(np.fft.fft(x)[0:N2]))
pylab.plot(F, X)
X[0] = 0 # remove DC value
pylab.xlabel('Frecuencia (kHz)')
pylab.ylabel('$\sqrt{|S(f)|}$')
pylab.show()
rango = [20.0, 1000.0] # el rango promedio de audicion humano en Hz
Fs = 20.5 * 1e3
T = 1.0 # segundos (t1-t0)
N = int(T*Fs)
n = np.arange(0,N)
F0 = (rango[1]-rango[0])*n/N + rango[0]
x = np.sin(np.pi*F0/Fs*n) #+ np.sin(np.pi*10000/Fs*n) # f=F0/Fs (discrete frequency)
plotSpecgram(x,Fs)
plotFFT(x,Fs)
wavfile.write('/tmp/barrido.wav',int(Fs),x.astype(np.float32))
Audio(x, rate=Fs)
La magnitud espectral de la señal de audio es una recta en el rango $[20, 20000]$ Hz, lo que significa que todas las frecuencias están presentes como era de esperar.
Procedemos a codificar y decodificar el barrido de frecuencias usando OPUS para una tasa de bits de 8 Kbps (la misma empleada en una videollamada entre dos personas) [12].
def readPlayVisualizeFile(inputFile):
fs, x = wavfile.read(inputFile)
y = np.array(x)/max(x)
plotSpecgram(x,fs)
return fs, x
# MP3
#!sudo apt-get install lame
#!lame -b8 --quiet /tmp/barrido_20_20k.wav /tmp/opusEnc.mp3
#!lame --quiet --decode /tmp/opusEnc.mp3 /tmp/opusDec.wav
#!ls -sh /tmp/opusDec.wav
# OPUS
#!sudo apt-get install ffmpeg
#!sudo apt-get install opus-tools
!ffmpeg -loglevel error -y -i /tmp/barrido.wav -qscale 0 /tmp/wavRaw.wav # opus-tools
!opusenc --quiet --bitrate 8 /tmp/wavRaw.wav /tmp/opusEnc.opus
!opusenc --quiet --bitrate 8 /tmp/barrido.wav /tmp/opusEnc.opus
!opusdec --quiet /tmp/opusEnc.opus /tmp/opusDec.wav
Fs, x = readPlayVisualizeFile('/tmp/opusDec.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
Como se observa, las frecuencias más altas que 5K Hz fueron truncadas completamente. Probemos ahora con 128 kbps (fullband stereo) a ver si la calidad mejora.
# MP3
!ffmpeg -loglevel error -y -i /tmp/barrido.wav -qscale 0 /tmp/wavRaw.wav # opus-tools
!opusenc --quiet --bitrate 128 /tmp/wavRaw.wav /tmp/opusEnc.opus
!opusenc --quiet --bitrate 128 /tmp/barrido.wav /tmp/opusEnc.opus
!opusdec --quiet /tmp/opusEnc.opus /tmp/opusDec.wav
Fs, x = readPlayVisualizeFile('/tmp/opusDec.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
Observamos que el contenido frecuencial ha sufrido muchos cambios, entre ellos, se introdujeron frecuencias parásitas producto del aliasing o del clipping), hay al menos 4 rectas principales en lugar de 2, también parece que se ha aplicado un filtro pasabajas con frecuencia de corte 20K Hz. Entonces sí que el codec Opus introduce distorsiones. Esto significa que si se aplica reconocimiento de patrones a una grabación procesada por Opus, el clasificador tendría información con mucho más ruido que con una grabación en formato WAV.
Aún se pueden apreciar los dos tonos ubicados cerca de 10K Hz, por lo que la distorsión introducida para el Opus no ha sido mucha. Cabe preguntarse, ¿esto sucede porque se está usando CELT en lugar de SILK? ¿Podría forzarse usar SILK?
Nota: a partir de este momento se recomienda usar audifonos para apreciar mejor la calidad el audio.
Ahora realicemos una prueba procesando una grabación empleada en el reconocimiento de sonidos del cuerpo (una disciplina llamada auscultación). Para tomamos una grabación del sitio https://www.kaggle.com/vbookshelf/respiratory-sound-database.
Fs, x = readPlayVisualizeFile('./wav/107_3p2_Tc_mc_AKGC417L_2.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
Observamos que hay energía alta en el rango $[0,1.5]$ y $[7.5,9.5]$ kHz.
Al parecer, la cantidad de bits usados tiene un efecto directamente proporcional sobre la frecuencia de corte. Además, observamos que la energía del rango $[0,1.5]$ se dispersó ahora por $[0,4.5]$ kHz y que el pic en $[7.5,9.5]$ kHz desapareció.
# OPUS
!ffmpeg -loglevel error -y -i ./wav/107_3p2_Tc_mc_AKGC417L_2.wav -qscale 0 /tmp/wavRaw.wav # same quality
!opusenc --quiet --bitrate 8 /tmp/wavRaw.wav /tmp/opusEnc.opus
!opusdec --quiet /tmp/opusEnc.opus /tmp/opusDec.wav
Fs, x = readPlayVisualizeFile('/tmp/opusDec.wav')
plotFFT(x,Fs)
Audio(x, rate=Fs)
En términos psicoacústicos, la distorsión del OPUS no parece estar en el enmascaramiento de frecuencias sino más bien en la presencia de contenido frecuencial que no estaba antes, producto de un fenómeno llamado clipping. Igualmente, esto podría afectar el rendimiento de un algoritmo de reconocimiento de patrones.

Este obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial-SinObraDerivada 4.0 Internacional. El sitio juanfonsecasolis.github.io es un blog dedicado a la investigación independiente en temas relacionados al procesamiento digital de señales. Para reutilizar este artículo y citar las fuente por favor utilice el siguiente Bibtex:
@online{Fonseca2020,
author = {Juan M. Fonseca-Solís},
title = { Pruebas por pares o pairwise testing},
year = 2020,
url = {https://juanfonsecasolis.github.io/blog/JFonseca.pairwisetesting.html},
urldate = {}
}